package io.cici.cc.mybatis.plus.core.metadata;

import io.cici.cc.mybatis.lite.mapping.ResultMapping;
import io.cici.cc.mybatis.lite.reflection.Reflector;
import io.cici.cc.mybatis.lite.session.Configuration;
import io.cici.cc.mybatis.lite.type.TypeHandler;
import io.cici.cc.mybatis.plus.annotation.FieldStrategy;
import io.cici.cc.mybatis.plus.annotation.SqlCondition;
import io.cici.cc.mybatis.plus.core.config.GlobalConfig;
import io.cici.cc.mybatis.plus.core.toolkit.Constants;
import io.cici.cc.mybatis.plus.core.toolkit.StringUtils;
import io.cici.cc.mybatis.plus.core.toolkit.sql.SqlScriptUtils;
import lombok.*;

import java.lang.reflect.Field;

/**
 * 数据库表字段反射信息
 */
@Getter
@ToString
@EqualsAndHashCode
public class Column implements Constants {

    private final Field field;
    /**
     * 字段名
     */
    private final String column;
    /**
     * 属性名
     */
    private final String property;
    /**
     * 属性表达式#{property}, 可以指定jdbcType, typeHandler等
     */
    private final String el;
    /**
     * jdbcType, typeHandler等部分
     */
    private final String mapping;
    /**
     * 属性类型
     */
    private final Class<?> propertyType;
    /**
     * 是否是基本数据类型
     */
    private final boolean isPrimitive;
    /**
     * 属性是否是 CharSequence 类型
     */
    private final boolean isCharSequence;
    /**
     * 字段验证策略之 insert Refer to {@link io.cici.cc.mybatis.plus.annotation.Column#insertStrategy()}
     */
    private final FieldStrategy insertStrategy;
    /**
     * 字段验证策略之 update Refer to {@link io.cici.cc.mybatis.plus.annotation.Column#updateStrategy()}
     */
    private final FieldStrategy updateStrategy;
    /**
     * 字段验证策略之 where Refer to {@link io.cici.cc.mybatis.plus.annotation.Column#whereStrategy()}
     */
    private final FieldStrategy whereStrategy;

    /**
     * 是否进行 select 查询
     * <p>
     * 大字段可设置为 false 不加入 select 查询范围
     * </p>
     */
    private boolean select = true;

    /**
     * 字段 update set 部分注入
     */
    private String update;
    /**
     * where 字段比较条件
     */
    private String condition = SqlCondition.EQUAL;

    /**
     * 缓存 sql select
     */
    @Setter(AccessLevel.NONE)
    private String sqlSelect;

    /**
     * 类型处理器
     */
    private Class<? extends TypeHandler<?>> typeHandler;


    /**
     * 全新的 存在 TableField 注解时使用的构造函数
     */

    public Column(GlobalConfig globalConfig, TableInfo tableInfo, Field field, io.cici.cc.mybatis.plus.annotation.Column tableField,
                  Reflector reflector) {

        GlobalConfig.DbConfig dbConfig = globalConfig.getDbConfig();
        field.setAccessible(true);
        this.field = field;

        this.property = field.getName();
        this.propertyType = reflector.getGetterType(this.property);
        this.isPrimitive = this.propertyType.isPrimitive();
        this.isCharSequence = StringUtils.isCharSequence(this.propertyType);

        this.update = tableField.update();

        final Class<? extends TypeHandler> typeHandler = tableField.typeHandler();

        boolean needAs = false;
        String el = this.property;
        if (StringUtils.isNotBlank(tableField.property())) {
            el = tableField.property();
            needAs = true;
        }

        {
            this.typeHandler = (Class<? extends TypeHandler<?>>) typeHandler;
            String type = tableField.type();
            el += (COMMA + "type=" + type);
            el += (COMMA + SqlScriptUtils.mappingTypeHandler(this.typeHandler));
        }

        this.el = el;
        int index = el.indexOf(COMMA);
        this.mapping = index > 0 ? el.substring(++index) : null;

        String column = tableField.value();
        if (StringUtils.isBlank(column)) {
            column = this.property;
            if (tableInfo.isUnderCamel()) {
                /* 开启字段下划线申明 */
                column = StringUtils.camelToUnderline(column);
            }
            if (dbConfig.isCapitalMode()) {
                /* 开启字段全大写申明 */
                column = column.toUpperCase();
            }
        }
        String columnFormat = dbConfig.getColumnFormat();
        if (StringUtils.isNotBlank(columnFormat) && tableField.keepGlobalFormat()) {
            column = String.format(columnFormat, column);
        }

        this.column = column;
        this.sqlSelect = column;
        this.sqlSelect += (AS + tableField.property());


        this.insertStrategy = this.chooseFieldStrategy(tableField.insertStrategy(), dbConfig.getInsertStrategy());
        this.updateStrategy = this.chooseFieldStrategy(tableField.updateStrategy(), dbConfig.getUpdateStrategy());
        this.whereStrategy = this.chooseFieldStrategy(tableField.whereStrategy(), dbConfig.getWhereStrategy());

        if (StringUtils.isNotBlank(tableField.condition())) {
            // 细粒度条件控制
            this.condition = tableField.condition();
        }

        // 字段是否注入查询
        this.select = tableField.select();
    }

    /**
     * 不存在 TableField 注解时, 使用的构造函数
     */
    public Column(GlobalConfig globalConfig, TableInfo tableInfo, Field field, Reflector reflector) {
        field.setAccessible(true);
        this.field = field;

        this.property = field.getName();
        this.propertyType = reflector.getGetterType(this.property);
        this.isPrimitive = this.propertyType.isPrimitive();
        this.isCharSequence = StringUtils.isCharSequence(this.propertyType);
        this.el = this.property;
        this.mapping = null;
        GlobalConfig.DbConfig dbConfig = globalConfig.getDbConfig();
        this.insertStrategy = dbConfig.getInsertStrategy();
        this.updateStrategy = dbConfig.getUpdateStrategy();
        this.whereStrategy = dbConfig.getWhereStrategy();

        String column = this.property;
        if (tableInfo.isUnderCamel()) {
            /* 开启字段下划线申明 */
            column = StringUtils.camelToUnderline(column);
        }
        if (dbConfig.isCapitalMode()) {
            /* 开启字段全大写申明 */
            column = column.toUpperCase();
        }

        String columnFormat = dbConfig.getColumnFormat();
        if (StringUtils.isNotBlank(columnFormat)) {
            column = String.format(columnFormat, column);
        }

        this.column = column;
        this.sqlSelect = column;
        this.sqlSelect += (AS + this.property);
    }

    /**
     * 优先使用单个字段注解，否则使用全局配置
     */
    private FieldStrategy chooseFieldStrategy(FieldStrategy fromAnnotation, FieldStrategy fromDbConfig) {
        return fromAnnotation == FieldStrategy.DEFAULT ? fromDbConfig : fromAnnotation;
    }


    /**
     * 获取 insert 时候插入值 sql 脚本片段
     * <p>
     * insert into table (字段) values (值)
     * </p>
     * <p>
     * 位于 "值" 部位
     * </p>
     *
     * <li>不生成 if 标签</li>
     *
     * @return sql 脚本片段
     */
    public String getInsertSqlProperty(final String prefix) {
        final String newPrefix = prefix == null ? EMPTY : prefix;
        return SqlScriptUtils.safeParam(newPrefix + el) + COMMA;
    }

    /**
     * 获取 insert 时候插入值 sql 脚本片段
     * <p>
     * insert into table (字段) values (值)
     * </p>
     * <p>
     * 位于 "值" 部位
     * </p>
     *
     * <li>根据规则会生成 if 标签</li>
     *
     * @return sql 脚本片段
     */
    public String getInsertSqlPropertyMaybeIf(final String prefix) {
        final String newPrefix = prefix == null ? EMPTY : prefix;
        String sqlScript = getInsertSqlProperty(newPrefix);

        return convertIf(sqlScript, newPrefix + property, insertStrategy);
    }

    /**
     * 获取 insert 时候字段 sql 脚本片段
     * <p>
     * insert into table (字段) values (值)
     * </p>
     * <p>
     * 位于 "字段" 部位
     * </p>
     *
     * <li>不生成 if 标签</li>
     *
     * @return sql 脚本片段
     */
    public String getInsertSqlColumn() {
        return column + COMMA;
    }

    /**
     * 获取 insert 时候字段 sql 脚本片段
     * <p>
     * insert into table (字段) values (值)
     * </p>
     * <p>
     * 位于 "字段" 部位
     * </p>
     *
     * <li>根据规则会生成 if 标签</li>
     *
     * @return sql 脚本片段
     */
    public String getInsertSqlColumnMaybeIf(final String prefix) {
        final String newPrefix = prefix == null ? EMPTY : prefix;
        final String sqlScript = getInsertSqlColumn();

        return convertIf(sqlScript, newPrefix + property, insertStrategy);
    }

    /**
     * 获取 set sql 片段
     *
     * @param prefix 前缀
     * @return sql 脚本片段
     */
    public String getSqlSet(final String prefix) {
        return getSqlSet(false, prefix);
    }

    /**
     * 获取 set sql 片段
     *
     * @param ignoreIf 忽略 IF 包裹
     * @param prefix   前缀
     * @return sql 脚本片段
     */
    public String getSqlSet(final boolean ignoreIf, final String prefix) {
        final String newPrefix = prefix == null ? EMPTY : prefix;
        // 默认: column=
        String sqlSet = column + EQUALS;
        if (StringUtils.isNotBlank(update)) {
            sqlSet += String.format(update, column);
        } else {
            sqlSet += SqlScriptUtils.safeParam(newPrefix + el);
        }
        sqlSet += COMMA;
        if (ignoreIf) {
            return sqlSet;
        }

        return convertIf(sqlSet, convertIfProperty(newPrefix, property), updateStrategy);
    }

    private String convertIfProperty(String prefix, String property) {
        return StringUtils.isNotBlank(prefix) ? prefix.substring(0, prefix.length() - 1) + "['" + property + "']"
                : property;
    }

    /**
     * 获取 查询的 sql 片段
     *
     * @param prefix 前缀
     * @return sql 脚本片段
     */
    public String getSqlWhere(final String prefix) {
        final String newPrefix = prefix == null ? EMPTY : prefix;
        // 默认: AND column=#{prefix + el}
        String sqlScript = " AND " + String.format(condition, column, newPrefix + el);
        // 查询的时候只判非空
        return convertIf(sqlScript, convertIfProperty(newPrefix, property), whereStrategy);
    }


    ResultMapping getResultMapping(final Configuration configuration) {
        TypeHandler typeHandler = null;
        ResultMapping.Builder builder = new ResultMapping.Builder(configuration, property, column, null, typeHandler);
        return builder.build();
    }

    /**
     * 转换成 if 标签的脚本片段
     *
     * @param sqlScript     sql 脚本片段
     * @param property      字段名
     * @param fieldStrategy 验证策略
     * @return if 脚本片段
     */
    private String convertIf(final String sqlScript, final String property, final FieldStrategy fieldStrategy) {
        if (fieldStrategy == FieldStrategy.NEVER) {
            return null;
        }
        if (isPrimitive || fieldStrategy == FieldStrategy.IGNORED || fieldStrategy == FieldStrategy.ALWAYS) {
            return sqlScript;
        }
        if (fieldStrategy == FieldStrategy.NOT_EMPTY && isCharSequence) {
            return SqlScriptUtils.convertIf(sqlScript, String.format("%s != null and %s != ''", property, property),
                    false);
        }
        return SqlScriptUtils.convertIf(sqlScript, String.format("%s != null", property), false);
    }
}
